import {
  BoxGeometry,
  BufferGeometry,
  CurvePath,
  CylinderGeometry,
  ExtrudeGeometry,
  Float32BufferAttribute,
  LineCurve3,
  Shape,
  ShapeGeometry,
  Vector2,
  Vector3,
} from "three";
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";

function cumsum(points) {
  const a = new Vector2();
  const b = new Vector2();
  const res = [];
  for (const pt of points) {
    b.set(pt[0], pt[1]);
    res.push(a.add(b).clone());
  }
  return res;
}

function removeIndicesFromGeometry(geometry) {
  if (!geometry.index) {
    console.log("Geometry already has no indices.");
    return geometry;
  }

  const indexArray = geometry.index.array;
  const positionArray = geometry.attributes.position.array;
  const normalArray = geometry.attributes.normal
    ? geometry.attributes.normal.array
    : null;
  const uvArray = geometry.attributes.uv ? geometry.attributes.uv.array : null;

  const newPositions = [];
  const newNormals = [];
  const newUvs = [];

  for (let i = 0; i < indexArray.length; i++) {
    const index = indexArray[i];

    // 展开位置数据
    newPositions.push(positionArray[index * 3 + 0]);
    newPositions.push(positionArray[index * 3 + 1]);
    newPositions.push(positionArray[index * 3 + 2]);

    // 如果有法线数据，展开法线数据
    if (normalArray) {
      newNormals.push(normalArray[index * 3 + 0]);
      newNormals.push(normalArray[index * 3 + 1]);
      newNormals.push(normalArray[index * 3 + 2]);
    }

    // 如果有UV数据，展开UV数据
    if (uvArray) {
      newUvs.push(uvArray[index * 2 + 0]);
      newUvs.push(uvArray[index * 2 + 1]);
    }
  }

  // 创建新的BufferGeometry
  const newGeometry = new BufferGeometry();

  // 设置新的位置属性
  newGeometry.setAttribute(
    "position",
    new Float32BufferAttribute(newPositions, 3)
  );

  // 如果有法线数据，设置新的法线属性
  if (newNormals.length > 0) {
    newGeometry.setAttribute(
      "normal",
      new Float32BufferAttribute(newNormals, 3)
    );
  }

  // 如果有UV数据，设置新的UV属性
  if (newUvs.length > 0) {
    newGeometry.setAttribute("uv", new Float32BufferAttribute(newUvs, 2));
  }

  return newGeometry;
}

class SolidAbutment {
  constructor(params = {}) {
    this.params = params;
    this.base = new Vector3(0, this.dY, -this.capHeight / 2);
    this.build();
  }

  get deckWidth() {
    return this.params?.deckWidth || 12.75;
  }

  build() {
    this.buildCap();
    this.buildBackWall();
    this.buildEarWall();
    this.buildStopper();
    this.buildColumn();
    this.geometry = mergeGeometries([
      ...[
        this.geomCap,
        this.geomBackWall,
        ...this.geomStoppers,
        ...this.geomColumns,
      ].map((g) => removeIndicesFromGeometry(g)),
      ...this.geomEarWalls,
    ]);
  }

  buildCap() {
    const cap = new BoxGeometry(this.deckWidth, this.thickY, this.capHeight);
    cap.translate(...this.base);

    this.geomCap = cap;
  }

  buildBackWall() {
    const backWall = new BoxGeometry(
      this.deckWidth,
      this.backWallY,
      this.backWallH
    );
    const offset = new Vector3(0, -this.backWallY / 2, +this.backWallH / 2);
    backWall.translate(...offset);
    this.geomBackWall = backWall;
  }

  buildEarWall() {
    this.geomEarWalls = [];
    const points = [
      [this.totalH, 0],
      [-this.totalH, 0],
      [0, this.earWallY],
      [this.earWall_h1, 0],
    ];
    const shape = new Shape(cumsum(points));
    const curvePath = new CurvePath();
    const dX = this.earWallX / 2;
    curvePath.add(
      new LineCurve3(new Vector3(dX, 0, 0), new Vector3(-dX, 0, 0))
    );
    const earWall = new ExtrudeGeometry(shape, {
      depth: 1.5,
      bevelEnabled: false,
      extrudePath: curvePath,
    });
    const offset = new Vector3((-this.deckWidth + this.earWallX) / 2, -this.backWallY, this.backWallH);
    const wall1 = earWall.clone().translate(...offset);
    this.geomEarWalls.push(wall1);
    offset.x = -(-this.deckWidth + this.earWallX) / 2;
    earWall.translate(...offset);
    this.geomEarWalls.push(earWall);
  }

  buildStopper() {
    this.geomStoppers = [];
    const stopper = new BoxGeometry(
      this.stopperX,
      this.stopperY,
      this.stopperH
    );
    const offsetY = this.stopperY / 2;
    const offsetH = this.stopperH / 2;
    const offsetX = this.deckWidth / 2 - this.stopperX / 2;

    {
      const innerStopper = stopper.clone();
      innerStopper.translate(-offsetX, offsetY, offsetH);
      this.geomStoppers.push(innerStopper);
    }
    {
      const outerStopper = stopper.clone();
      outerStopper.translate(offsetX, offsetY, offsetH);
      this.geomStoppers.push(outerStopper);
    }
  }

  buildColumn() {
    this.geomColumns = [];
    const columnBase = new CylinderGeometry(
      this.columnRadius,
      this.columnRadius,
      this.columnH
    );
    let baseX = (-this.columnX * (this.columnCount - 1)) / 2;
    {
      for (let i = 0; i < this.columnCount; i++) {
        const column = columnBase.clone();
        column
          .rotateX(Math.PI / 2)
          .translate(
            baseX + this.columnX * i,
            this.columnDY,
            -this.columnH / 2
          );
        this.geomColumns.push(column);
      }
    }
  }

  get columnCount() {
    return 3;
  }
  get columnX() {
    return 4.5;
  }

  get columnDiameter() {
    return 1.5;
  }
  get columnRadius() {
    return this.columnDiameter / 2;
  }

  get columnH() {
    return 5;
  }

  get columnDY() {
    return -this.backWallY + this.thickY / 2;
  }

  get dY() {
    return this.thickY / 2 - this.backWallY;
  }

  get backWallY() {
    return 0.6;
  }

  get stopperY() {
    return this.thickY - this.backWallY;
  }

  get beamH() {
    return 1.2;
  }

  get bearingH() {
    return 0.3;
  }

  get stopperX() {
    return 0.3;
  }

  get stopperH() {
    return 0.5;
  }

  get earWallX() {
    return 0.5;
  }

  get earWallY() {
    return 2.4;
  }
  get earWall_h1() {
    return 0.75;
  }

  get earWallH() {
    return this.totalH - 0.2;
  }

  get totalH() {
    return this.backWallH + this.capHeight;
  }

  get backWallH() {
    return this.beamH + this.bearingH;
  }

  get capHeight() {
    return 0.8;
  }
  get thickY() {
    return 1.8;
  }
}

export default SolidAbutment;
